package org.daochong.lang.security;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.CRLExtensions;
import sun.security.x509.CRLNumberExtension;
import sun.security.x509.CRLReasonCodeExtension;
import sun.security.x509.CertificateValidity;
import sun.security.x509.DistributionPoint;
import sun.security.x509.FreshestCRLExtension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.KeyIdentifier;
import sun.security.x509.URIName;
import sun.security.x509.X500Name;
import sun.security.x509.X509CRLEntryImpl;
import sun.security.x509.X509CRLImpl;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

@SuppressWarnings("all")
public class DaoChongCRL {

	public static final int KEY_COMPROMISE = 1;
	public static final int CA_COMPROMISE = 2;
	public static final int AFFLIATION_CHANGED = 3;
	public static final int SUPERSEDED = 4;
	public static final int CESSATION_OF_OPERATION = 5;
	public static final int CERTIFICATE_HOLD = 6;
	public static final int REMOVE_FROM_CRL = 8;
	public static final int PRIVILEGE_WITHDRAWN = 9;
	public static final int AA_COMPROMISE = 10;

	public static X509CRL loadCRLS(byte[] data) {
		try {
			return new X509CRLImpl(data);
		} catch (Exception e) {
			throw new RuntimeException("Create CRLS FAIL", e);
		}
	}

	public static X509CRL loadCRLS(String filePath) {
		try {
			return (X509CRL) CertificateFactory.getInstance("X509").generateCRL(new FileInputStream(filePath));
		} catch (Exception e) {
			throw new RuntimeException("Create CRLS FAIL", e);
		}
	}

	public static X509CRL loadCRLS(InputStream in) {
		try {
			return (X509CRL) CertificateFactory.getInstance("X509").generateCRL(in);
		} catch (Exception e) {
			throw new RuntimeException("LOAD CRLS FAIL", e);
		}
	}

	private List<X509CRLEntry> entrys = new ArrayList<X509CRLEntry>();
	private List<X509CRLEntry> entrysAdd = new ArrayList<X509CRLEntry>();
	private PrivateKey privateKey;
	private PublicKey publicKey;
	private X500Name x500Name;
	private CRLExtensions extensions;
	private Date start;
	private Date end;
	private String path;
	private OutputStream outputStream;
	private BigInteger crlNumber;
	private List<String> crls;

	public List<String> getCrls() {
		return crls;
	}

	public void setCrls(List<String> crls) {
		this.crls = crls;
	}

	public void setCrls(String crl) {
		if (this.crls == null) {
			this.crls = new ArrayList<String>();
		}
		this.crls.add(crl);
	}

	public BigInteger getCrlNumber() {
		return crlNumber;
	}

	public void setCrlNumber(BigInteger crlNumber) {
		this.crlNumber = crlNumber;
	}

	public DaoChongCRL() {

	}

	public DaoChongCRL(StoreData data) {
		init(DaoChongCertificate.load(data), null);
	}

	public DaoChongCRL(StoreData data, String alias) {
		init(DaoChongCertificate.load(data), alias);
	}

	public DaoChongCRL(StoreData data, int expriedDays) {
		this.start = new Date();
		this.end = new Date(System.currentTimeMillis() + 86400000L * expriedDays);
		init(DaoChongCertificate.load(data), null);
	}

	public DaoChongCRL(StoreData data, String alias, int expriedDays) {
		this.start = new Date();
		this.end = new Date(System.currentTimeMillis() + 86400000L * expriedDays);
		init(DaoChongCertificate.load(data), alias);
	}

	public DaoChongCRL(DaoChongCertificate cert) {
		init(cert, null);
	}

	public DaoChongCRL(DaoChongCertificate cert, String alias) {
		init(cert, alias);
	}

	public DaoChongCRL(DaoChongCertificate cert, String alias, int expriedDays) {
		this.start = new Date();
		this.end = new Date(System.currentTimeMillis() + 86400000L * expriedDays);
		init(cert, alias);
	}

	public DaoChongCRL(DaoChongCertificate cert, int expriedDays) {
		this.start = new Date();
		this.end = new Date(System.currentTimeMillis() + 86400000L * expriedDays);
		init(cert, null);
	}

	public DaoChongCRL(CertificateRequest request) {
		init(request);
	}

	public DaoChongCRL(CertificateRequest request, PublicKey publicKey, PrivateKey privateKey) {
		this.publicKey = publicKey;
		this.privateKey = privateKey;
		init(request);
	}

	private void init(DaoChongCertificate store, String rootAlias) {
		try {
			if (rootAlias == null) {
				rootAlias = store.getDefaultPrivate();
			}
			X509CertImpl cert = (X509CertImpl) store.getCertificate(rootAlias);
			this.publicKey = cert.getPublicKey();
			this.privateKey = store.getPrivateKey(rootAlias);
			if (this.end == null) {
				X509CertInfo info = (X509CertInfo) cert.get(X509CertImpl.NAME + "." + X509CertImpl.INFO);
				CertificateValidity validata = (CertificateValidity) info.get(X509CertInfo.VALIDITY);
				this.end = (Date) validata.get(CertificateValidity.NOT_AFTER);
				if (this.start == null) {
					this.start = (Date) validata.get(CertificateValidity.NOT_BEFORE);
				}
			}
			if (this.start == null) {
				this.start = new Date();
			}
			this.x500Name = new X500Name(cert.getSubjectDN().getName());
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException("Create X500Name Fail", e);
		}
	}

	private void init(CertificateRequest request) {
		try {
			this.x500Name = new X500Name(request.toX500Name());
		} catch (Exception e) {
			throw new RuntimeException("Create X500Name Fail", e);
		}
		this.start = new Date();
		this.end = new Date(System.currentTimeMillis() + 86400000L * request.getExpiredDays());
	}

	public PublicKey getPublicKey() {
		return publicKey;
	}

	public void setPublicKey(PublicKey publicKey) {
		this.publicKey = publicKey;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public OutputStream getOutputStream() {
		if (this.outputStream == null) {
			try {
				return new FileOutputStream(this.getPath());
			} catch (Throwable e) {
				throw new RuntimeException("Not OutputStream ");
			}
		}
		return outputStream;
	}

	public void setOutputStream(OutputStream outputStream) {
		this.outputStream = outputStream;
	}

	public PrivateKey getPrivateKey() {
		return privateKey;
	}

	public void setPrivateKey(PrivateKey privateKey) {
		this.privateKey = privateKey;
	}

	public X500Name getX500Name() {
		return x500Name;
	}

	public void setX500Name(X500Name x500Name) {
		this.x500Name = x500Name;
	}

	public CRLExtensions getExtensions() {
		return extensions;
	}

	public void setExtensions(CRLExtensions extensions) {
		this.extensions = extensions;
	}

	public Date getStart() {
		if (this.start == null) {
			return new Date();
		}
		return start;
	}

	public void setStart(Date start) {
		this.start = start;
	}

	public Date getEnd() {
		return end;
	}

	public List<X509CRLEntry> getEntrys() {
		return entrys;
	}

	public List<X509CRLEntry> getEntrysAdd() {
		return entrysAdd;
	}

	public void setEnd(Date end) {
		this.end = end;
	}

	public void revoke(String certPath) {
		revoke(DaoChongCertificate.loadCert(certPath), new Date(), AA_COMPROMISE);
	}

	public void revoke(String certPath, Date date) {
		revoke(DaoChongCertificate.loadCert(certPath), date, AA_COMPROMISE);
	}

	public void revoke(String certPath, int reason) {
		revoke(DaoChongCertificate.loadCert(certPath), new Date(), reason);
	}

	public void revoke(String certPath, Date date, int reason) {
		revoke(DaoChongCertificate.loadCert(certPath), date, reason);
	}

	public void revoke(Certificate cert) {
		revoke(cert, new Date(), AA_COMPROMISE);
	}

	public void revoke(Certificate cert, Date date) {
		revoke(cert, date, AA_COMPROMISE);
	}

	public void revoke(Certificate cert, int reason) {
		revoke(cert, new Date(), reason);
	}

	public void revoke(Certificate cert, Date date, int reason) {
		revoke(((X509Certificate) cert).getSerialNumber(), date, reason);
	}

	public void revoke(BigInteger serialNumber) {
		revoke(serialNumber, new Date(), AA_COMPROMISE);
	}

	public void revoke(BigInteger serialNumber, Date date) {
		revoke(serialNumber, date, AA_COMPROMISE);
	}

	public void revoke(BigInteger serialNumber, int reason) {
		revoke(serialNumber, new Date(), reason);
	}

	public void revoke(BigInteger serialNumber, Date date, int reason) {
		try {
			if (serialNumber == null) {
				throw new RuntimeException("serialNumber is Null");
			}
			if (date == null) {
				throw new RuntimeException("Revoke Date is Null");
			}
			if (reason < 1 || reason > 10) {
				throw new RuntimeException("reason is not validata");
			}
			CRLExtensions crlExts = new CRLExtensions();
			CRLReasonCodeExtension cce = new CRLReasonCodeExtension(reason);
			crlExts.set("2.5.29.21", cce);
			X509CRLEntryImpl entry = new X509CRLEntryImpl(serialNumber, date, crlExts);
			this.entrysAdd.add(entry);
		} catch (Throwable e) {

		}

	}

	public void save(OutputStream out, BigInteger crlNumber) {
		try {
			if (this.x500Name == null) {
				throw new RuntimeException("x500Name is Null");
			}
			if (this.start == null) {
				throw new RuntimeException("Start Date is Null");
			}
			if (this.end == null) {
				throw new RuntimeException("End Date is Null");
			}
			if (this.getPublicKey() != null) {
				if (this.extensions == null) {
					this.extensions = new CRLExtensions();
				}
				KeyIdentifier keyId = new KeyIdentifier(getPublicKey());
				AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension(keyId, null, null);
				extensions.set(CertificateRequest.OID_AuthorityKeyIdentifier, authKeyId);
			}

			if (crlNumber != null && crlNumber.longValue() > 0) {
				if (this.extensions == null) {
					this.extensions = new CRLExtensions();
				}
				CRLNumberExtension number = new CRLNumberExtension(crlNumber);
				this.extensions.set(CertificateRequest.OID_CRLNumber, number);
			}
			if (this.crls != null && this.crls.size() > 0) {
				if (this.extensions == null) {
					this.extensions = new CRLExtensions();
				}
				List<DistributionPoint> list = new ArrayList<DistributionPoint>();
				for (String crl : this.getCrls()) {
					GeneralNames names = new GeneralNames();
					names.add(new GeneralName(new URIName(crl)));
					GeneralNames names1 = new GeneralNames();
					names1.add(new GeneralName(this.getX500Name()));
					DistributionPoint point = new DistributionPoint(names1, null, names);
					list.add(point);
				}
				FreshestCRLExtension crle = new FreshestCRLExtension(list);
				this.extensions.set(CertificateRequest.OID_FreshestCRL, crle);
			}

			X509CRLImpl crls = null;
			if (this.entrysAdd.size() > 0 || this.entrysAdd.size() > 0) {
				X509CRLEntry[] ar = new X509CRLEntry[entrysAdd.size() + entrys.size()];
				int pos = 0;
				for (X509CRLEntry entry : entrys) {
					ar[pos++] = entry;
				}
				for (X509CRLEntry entry : entrysAdd) {
					ar[pos++] = entry;
				}
				if (this.extensions != null) {
					crls = new X509CRLImpl(this.getX500Name(), this.getStart(), this.getEnd(), ar, this.extensions);
				} else {
					crls = new X509CRLImpl(this.getX500Name(), this.getStart(), this.getEnd(), ar);
				}
			} else {
				if (this.extensions != null) {
					crls = new X509CRLImpl(this.getX500Name(), this.getStart(), this.getEnd(), new X509CRLEntry[0],
							this.extensions);
				} else {
					crls = new X509CRLImpl(this.getX500Name(), this.getStart(), this.getEnd());
				}
			}
			crls.sign(getPrivateKey(), "SHA1WithRSA");
			out.write(crls.getEncoded());
			out.close();
		} catch (Throwable e) {
			throw new RuntimeException("Save CRL Fail", e);
		}
	}

	public void save(BigInteger crlNumber) {
		save(getOutputStream(), crlNumber);
	}

	public void save(int crlNumber) {
		save(getOutputStream(), new BigInteger(String.valueOf(crlNumber)));
	}

	public void save() {
		save(getOutputStream(), this.getCrlNumber());
	}
}
